04. Rotate the Star

L3 03 Rotation

Rotate the Star

In this step, you will implement the rotater() function for the rotateButton click handler, which will cause the star to rotate in a circle.

Step 1: Creating the animator

  1. Inside therotater() function, create an animation that rotates the ImageView containing the star from a value of -360 to 0. This means that the view, and thus the star inside it, will rotate in a full circle (360 degrees) around its center.
val animator = ObjectAnimator.ofFloat(star, View.ROTATION, -360f, 0f)

Note: The reason that the animation starts at -360 is that that allows the star to complete a full circle (360 degrees) and end at 0, which is the default rotation value for a non-rotated view, so it’s a good value to have at the end of the animation (in case any other action occurs on that view later, expecting the default value).

This line of code creates an ObjectAnimator that acts on the target “star” (the ImageView instance that holds our star graphic). It runs an animation on the ROTATION property of star. Changes to that property will cause the star to rotate around its center. There are two other rotation properties (ROTATION_X and ROTATION_Y) that rotate around the other axes (in 3D), but these are not typically used in UI animations, since UIs are typically 2D.

Note: A property, to the animation system, is a field that is exposed via setters and getters, either implicitly (as properties are in Kotlin) or explicitly (via the setter/getter pattern in the Java programming language). There are also a special case of properties exposed via the class android.util.Property which is used by the View class, which allows a type-safe approach for animations, as we’ll see later. The Animator system in Android was specifically written to animate properties, meaning that it can animate anything (not just UI elements) that has a setter (and, in some cases, a getter)

Note: View properties are a set of functionality added to the base View class that allows all views to be transformed in specific ways that are useful in UI animations. There are properties for position (called “translation”), rotation, scale, and transparency (called “alpha”).

There are actually two different ways to access the properties, by regular setter/getter pairs, like setTranslateX()/getTranslateX(), and by static android.util.Property objects, like View.TRANSLATE_X (an object that has both a get() and a set() method). This lesson primarily uses android.util.Propertyobjects, because it has less overhead internally along with better type-safety, but you can also use the setters and getters of any object, as you will see toward the end of the lab.

Note: Property animation is, simply, the changing of property values over time. ObjectAnimator was created to provide a simple set-and-forget mechanism for animating properties. For example, you can define an animator that changes the “alpha” property of a View, which will alter the transparency of that view on the screen.

The animation runs from a start value of -360 degrees to an end value of 0 degrees, which will spin the star in a single rotation about its center. Note that the start starts at 0 degrees, before the animation begins, and then jumps immediately to -360 degrees. But since -360 is visually the same as 0 degrees, there is no noticeable change when the animation begins.

  1. Now you can run the app again. Click on the ROTATE button and you will notice… nothing. We’ve set up the animation, but we haven’t yet told it to run. Let’s do that.

Step 2: Running the animation

  1. Below the animator, add a single line that starts it:
animator.start()

Now if you run the application again and click on ROTATE, you will see that the star does, indeed, spin around its center. But it does so really quickly. In fact, it does it in 300 milliseconds, which is the default duration of all animations on the platform. 300 milliseconds is a decent default for most animations, but in this case, we’d like more time to enjoy the animation.

  1. Change the duration property of the animator to 1000 milliseconds by adding a single line of code between the previous two lines:
val animator = ObjectAnimator.ofFloat(star, View.ROTATION, -360f, 0f)
animator.duration = 1000
animator.start()

You’re almost there. If you run the app, you’ll see that it has a nice animation that lasts for a second. All good, right?

Step 3: Avoiding discontinuous motion

Well… maybe you’re impatient, like I am, and you run the animation again before it’s come to a stop. Do you notice a jump when you click on the button? This is because we always reset to -360 degrees at the start of the animation, regardless of whether the star is currently in the middle of animating or not. This discontinuous motion is an example of what we call “jank”; it causes a disruptive flow for the user, instead of the smooth experience you would like.

There are different ways of dealing with this situation (such as starting the new animation from whatever the current value is). But to keep things simple, you’re going to just prevent the user from clicking the button while the animation is running, to allow them to fully enjoy the in-process animation first.

Animators have a concept of listeners, which call back into user code to notify the application of changes in the state of the animation. There are callbacks for an animation starting, ending, pausing, resuming, and repeating. What we care about here are just the start and end events; we’d like to disable the ROTATE button as soon as the animation starts, and then re-enable it when the animation ends.

  1. Add a new AnimatorListenerAdapter object to the animator and override the onAnimationStart() and onAnimationEnd() methods.

Note: This code uses an adapter class (which provides default implementations of all of the listener methods) instead of implementing the raw AnimatorListener interface. The code only needs to know about a couple of the callbacks; it’s not worth implementing the rest of the listener methods, so we let the adapter stub them out for us.

val animator = ObjectAnimator.ofFloat(star, View.ROTATION, -360f, 0f)
animator.duration = 1000
animator.addListener(object : AnimatorListenerAdapter() {
    override fun onAnimationStart(animation: Animator?) {
        rotateButton.isEnabled = false
    }
    override fun onAnimationEnd(animation: Animator?) {
        rotateButton.isEnabled = true
    }
})
animator.start()

Here, rotateButton is disabled as soon as the animation starts and re-enabled when the animation ends. This way, each animation is completely separate from any other rotation animation, avoiding the jank of restarting in the middle.

That’s it for this first task; you now have an application that can launch rotation animations on the star. Take it for a spin!